package cc.wfu.infrastructure.persistent.repository;

import cc.common.types.constants.Constants;
import cc.common.types.entity.ProductActivityEntity;
import cc.wfu.domain.order.event.ActivitySkuStockZeroMessageEvent;
import cc.wfu.domain.order.model.aggregate.CreateOrderAggregate;
import cc.wfu.domain.order.model.entity.PayOrderEntity;
import cc.wfu.domain.order.model.entity.ProductActivitySkuEntity;
import cc.wfu.domain.order.model.entity.ProductOrderEntity;
import cc.wfu.domain.order.model.entity.UnpaidOrderEntity;
import cc.wfu.domain.order.model.valobj.ActivitySkuStockKeyVO;
import cc.wfu.domain.order.model.valobj.PayStatusVO;
import cc.wfu.domain.order.repository.IProductActivityRepository;
import cc.wfu.feign.client.ProductFeignClient;
import cc.wfu.infrastructure.event.EventPublisher;
import cc.wfu.infrastructure.persistent.dao.IProductOrderDao;
import cc.wfu.infrastructure.persistent.dao.IProductSkuDao;
import cc.wfu.infrastructure.persistent.po.ProductOrder;
import cc.wfu.infrastructure.persistent.po.ProductSku;
import cc.wfu.infrastructure.persistent.redis.IRedisService;
import cc.wfu.types.common.RedisKey;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @Author: jia-qiang ljq1024.cc
 * @desc:  活动仓储接口 - 实现
 * @Date: 2024-06-05-22:26
 */
@Slf4j
@Repository
public class ProductActivityRepository implements IProductActivityRepository {

    @Resource
    private EventPublisher eventPublisher;
    @Resource
    private IRedisService redisService;
    @Resource
    private ProductFeignClient productFeignClient;
    @Resource
    private ActivitySkuStockZeroMessageEvent activitySkuStockZeroMessageEvent;

    @Resource
    private IProductSkuDao productSkuDao;
    @Resource
    private IProductOrderDao productOrderDao;

    /**
     * 通过sku查询sku信息
     * @param sku sku ID
     * @return sku 实体
     */
    @Override
    public ProductActivitySkuEntity queryActivitySku(Long sku) {
        ProductSku productSku = productSkuDao.queryActivitySku(sku);
        return ProductActivitySkuEntity.builder()
                .sku(productSku.getSku())
                .productId(productSku.getProductId())
                .stockCount(productSku.getStockCount())
                .stockCountSurplus(productSku.getStockCountSurplus())
                .build();
    }

    /**
     * 缓存 sku库存
     * @param sku sku
     * @param stockCount sku 库存
     */
    @Override
    public void cacheActivitySkuStockCount(Long sku, Integer stockCount) {
        String cacheKey = RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
        if (redisService.isExists(cacheKey)) return;
        redisService.setAtomicLong(cacheKey, stockCount);
    }

    /**
     * 预热活动【查询时预热到缓存】
     * @param productId 商品ID
     */
    @Override
    public ProductActivityEntity queryProductActivityByProductId(Long productId) {
        // 优先从缓存获取
        String cacheKey = RedisKey.PRODUCT_ACTIVITY_KEY + productId;
        ProductActivityEntity productActivityEntity = redisService.getValue(cacheKey, ProductActivityEntity.class);
        if (null != productActivityEntity) return productActivityEntity;
        // 从库中获取数据
        productActivityEntity = productFeignClient.queryProductByProductId(productId).getData();
        if (productActivityEntity != null)
            redisService.setValue(cacheKey, productActivityEntity);
        return productActivityEntity;
    }


    /**
     * 根据商品ID，扣减奖品缓存库存
     * @param activitySkuStockKeyVO 互动SKU
     * @param endDateTime 活动结束时间，根据结束时间设置加锁的key为结束时间
     */
    @Override
    public boolean subtractionActivitySkuStock(ActivitySkuStockKeyVO activitySkuStockKeyVO, Date endDateTime) {
        String cacheKey = RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + activitySkuStockKeyVO.getSku();
        long surplus = redisService.decr(cacheKey);
        if (surplus == 0) {
            // 库存消耗没了以后，发送MQ消息，更新数据库库存
            eventPublisher.publish(activitySkuStockZeroMessageEvent.topic(), activitySkuStockZeroMessageEvent.buildEventMessage(activitySkuStockKeyVO));
            // 先减少后判断，为零说明减少前为1,返回true
            return true;
        } else if (surplus < 0) {
            // 库存小于0，恢复为0个
            redisService.setAtomicLong(cacheKey, 0);
            return false;
        }
        // 1. 按照cacheKey decr 后的值，如 99、98、97 和 key 组成为库存锁的key进行使用。
        // 2. 加锁为了兜底，如果后续有恢复库存，手动处理等【运营是人来操作，会有这种情况发放，系统要做防护】，也不会超卖。因为所有的可用库存key，都被加锁了。
        // 3. 设置加锁时间为活动到期 + 延迟1天
        String lockKey = cacheKey + Constants.UNDER_LINE + surplus;
        long expireMillis = endDateTime.getTime() - System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
        Boolean lock = redisService.setNx(lockKey, expireMillis, TimeUnit.MILLISECONDS);
        if (Boolean.FALSE.equals(lock)) {
            log.error("活动sku库存加锁失败 {}", lockKey);
        }
        return lock;
    }

    /** 写入延迟队列，延迟消费更新库存记录 */
    @Override
    public void activitySkuStockConsumeSendQueue(ActivitySkuStockKeyVO activitySkuStockKeyVO) {
        String cacheKey = RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY;
        // 通过 Redisson 客户端的 getBlockingQueue 方法获取一个阻塞队列对象 blockingQueue。
        // 这个阻塞队列用于存储 ActivitySkuStockKeyVO 类型的对象
        RBlockingQueue<ActivitySkuStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);
        // Redisson 客户端的 getDelayedQueue 方法获取一个延迟队列对象 delayedQueue
        RDelayedQueue<ActivitySkuStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);
        // strategyAwardStockKeyVO 对象将在当前时间的基础上延迟 3 秒后才会被取出
        delayedQueue.offer(activitySkuStockKeyVO, 3, TimeUnit.SECONDS);
    }

    /**
     * 保存订单
     * @param createOrderAggregate 订单聚合对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class, timeout = 350, propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void doSaveOrder(CreateOrderAggregate createOrderAggregate) {
        // 1. 构建订单对象
        ProductOrderEntity productOrderEntity = createOrderAggregate.getProductOrderEntity();
        ProductOrder productOrder = ProductOrder.builder()
                .productId(productOrderEntity.getProductId())
                .productName(productOrderEntity.getProductName())
                .productOrderId(productOrderEntity.getOrderId())
                .userId(Long.valueOf(productOrderEntity.getUserId()))
                .totalAmount(productOrderEntity.getTotalAmount())
                .payStatus(PayStatusVO.WAIT.getCode())
                .orderStatus(productOrderEntity.getState().getCode())
                .orderTime(productOrderEntity.getOrderTime())
                .sku(productOrderEntity.getSku())
                .outBusinessNo(productOrderEntity.getOutBusinessNo())
                .payType(0)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .build();

        // 2. 插入数据库
        productOrderDao.insertOne(productOrder);
    }

    /**
     * 清空商品sku库存
     * @param sku sku
     */
    @Override
    public void clearActivitySkuStock(Long sku) {
        productSkuDao.clearActivitySkuStock(sku);
    }

    /**
     * 获取活动sku库存消耗队列
     *
     * @return 商品库存Key信息
     */
    @Override
    public ActivitySkuStockKeyVO takeQueueValue() {
        String cacheKey = RedisKey.ACTIVITY_SKU_COUNT_QUERY_KEY;
        RBlockingQueue<ActivitySkuStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);
        return destinationQueue.poll();
    }

    /**
     * 延迟队列 + 任务 趋势更新活动sku库存
     *
     * @param sku 活动商品
     */
    @Override
    public void updateActivitySkuStock(Long sku) {
        String cacheKey = RedisKey.ACTIVITY_SKU_STOCK_COUNT_KEY + sku;
        long surplus = redisService.getAtomicLong(cacheKey);
        if (surplus <= 0) {
            log.warn("商品sku:{} 库存已经为 0", sku);
            return;
        }
        productSkuDao.updateActivitySkuStock(sku);
    }

    /**
     * 超时关单
     * @param orderId 订单ID
     */
    @Override
    public boolean changeOrderClose(String orderId) {
        return productOrderDao.changeOrderClose(orderId);
    }

    /**
     * 查询超时订单列表
     * @return 超时订单单号列表
     */
    @Override
    public List<String> queryTimeoutCloseOrderList() {
        return productOrderDao.queryTimeoutCloseOrderList();
    }

    /**
     * 查询有效期内，未接收到支付回调的订单
     */
    @Override
    public List<String> queryNoPayNotifyOrder() {
        return productOrderDao.queryNoPayNotifyOrder();
    }

    /**
     * 查询待补货订单
     */
    @Override
    public List<String> queryReplenishmentOrder() {
        return productOrderDao.queryReplenishmentOrder();
    }

    /**
     * 订单商品发货
     * @param orderId 订单ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class, timeout = 350, propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
    public void deliverGoods(String orderId) {
        // todo 模拟发货即可
        productOrderDao.deliverGoods(orderId);
    }

    /**
     * 查询未支付订单
     */
    @Override
    public UnpaidOrderEntity queryUnpaidOrder(CreateOrderAggregate createOrderAggregate) {
        // 1. 准备参数
        ProductOrder productOrderReq = ProductOrder.builder()
                .userId(Long.valueOf(createOrderAggregate.getUserId()))
                .productId(createOrderAggregate.getProductId())
                .build();
        // 2. 查询
        ProductOrder productOrder = productOrderDao.queryUnpaidOrder(productOrderReq);
        if (productOrder == null) return null;
        // 3. 类型转换
        return UnpaidOrderEntity.builder()
                .totalAmount(productOrder.getTotalAmount())
                .productName(productOrder.getProductName())
                .payUrl(productOrder.getPayUrl())
                .payStatus(PayStatusVO.get(productOrder.getPayStatus()))
                .build();
    }

    /**
     * 支付成功 - 更新订单信息
     * @param payOrderEntity 订单支付信息
     */
    @Override
    public void updateOrderPayInfo(PayOrderEntity payOrderEntity) {
        // 1. 准备参数
        ProductOrder productOrderReq = ProductOrder.builder()
                .userId(Long.valueOf(payOrderEntity.getUserId()))
                .productOrderId(payOrderEntity.getOrderId())
                .payStatus(payOrderEntity.getPayStatus().getCode())
                .payUrl(payOrderEntity.getPayUrl())
                .updateTime(LocalDateTime.now())
                .build();
        // 2. 更新订单信息
        productOrderDao.updateOrderPayInfo(productOrderReq);
    }
}
